---
title: "动态内存分配"
description: "从 JavaScript 视角学习 C 语言动态内存分配，理解 malloc、free、内存泄漏，并对比 JavaScript 垃圾回收机制。"
---

# 动态内存分配

## 1. 概念介绍

### 从 JavaScript 垃圾回收到 C 手动内存管理

在 JavaScript 中，内存管理是自动的 - 垃圾回收器会自动释放未使用的内存。而在 C 语言中，你必须手动使用 `malloc()`、`calloc()`、`realloc()` 和 `free()` 等函数来分配和释放内存。

> 💡 **核心概念**：C 的手动内存管理给你精确的控制权，但需要仔细注意以防止内存泄漏和未定义行为。

## 2. 基础内存分配

<UniversalEditor title="基础内存分配" compare={true}>
```javascript !! js
// JavaScript - 自动内存管理
let arr = [1, 2, 3, 4, 5];
arr.push(6); // 数组自动增长
arr = null; // 内存将被垃圾回收

// 动态对象创建
let obj = { name: "Alice", age: 30 };
obj = null; // 对象将被垃圾回收

// 无需手动内存管理
```

```c !! c
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 为整数分配内存
    int* ptr = (int*)malloc(sizeof(int));
    if (ptr == NULL) {
        printf("内存分配失败！\n");
        return 1;
    }
    
    *ptr = 42;
    printf("值: %d\n", *ptr);
    
    // 始终释放分配的内存
    free(ptr);
    ptr = NULL; // 良好实践，避免悬空指针
    
    // 为数组分配内存
    int* arr = (int*)malloc(5 * sizeof(int));
    if (arr == NULL) {
        printf("数组分配失败！\n");
        return 1;
    }
    
    // 初始化数组
    for (int i = 0; i < 5; i++) {
        arr[i] = i + 1;
    }
    
    // 使用数组
    for (int i = 0; i < 5; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    // 释放数组内存
    free(arr);
    arr = NULL;
    
    return 0;
}
```
</UniversalEditor>

## 3. 高级内存函数

<UniversalEditor title="高级内存函数" compare={true}>
```javascript !! js
// JavaScript - 动态数组和对象
let numbers = [];
for (let i = 0; i < 10; i++) {
    numbers.push(i);
}

// 数组自动调整大小
numbers.length = 20; // 扩展数组
numbers.length = 5;  // 截断数组

// 对象属性可以动态添加/删除
let person = {};
person.name = "Bob";
person.age = 25;
delete person.age;
```

```c !! c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    // calloc - 分配并初始化为零
    int* zeros = (int*)calloc(5, sizeof(int));
    if (zeros == NULL) {
        printf("calloc 失败！\n");
        return 1;
    }
    
    printf("calloc 初始化的值: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", zeros[i]); // 全为零
    }
    printf("\n");
    
    // realloc - 调整现有分配的大小
    int* resized = (int*)realloc(zeros, 10 * sizeof(int));
    if (resized == NULL) {
        printf("realloc 失败！\n");
        free(zeros);
        return 1;
    }
    
    // 初始化新元素
    for (int i = 5; i < 10; i++) {
        resized[i] = i;
    }
    
    printf("realloc 后: ");
    for (int i = 0; i < 10; i++) {
        printf("%d ", resized[i]);
    }
    printf("\n");
    
    free(resized);
    
    return 0;
}
```
</UniversalEditor>

## 4. 内存泄漏和预防

<UniversalEditor title="内存泄漏预防" compare={true}>
```javascript !! js
// JavaScript - 自动清理
function createObjects() {
    let obj = { data: "重要数据" };
    // obj 在函数结束时将被垃圾回收
    // 无需手动清理
}

// 即使有循环引用，垃圾回收器也能处理
let obj1 = { name: "Alice" };
let obj2 = { name: "Bob" };
obj1.friend = obj2;
obj2.friend = obj1;
// 循环引用将被检测并清理
```

```c !! c
#include <stdio.h>
#include <stdlib.h>

// 错误：内存泄漏 - 分配的内存未释放
void badFunction() {
    int* ptr = (int*)malloc(sizeof(int));
    *ptr = 42;
    // 内存泄漏：ptr 未释放！
    // ptr 超出作用域，内存丢失
}

// 正确：适当的内存管理
void goodFunction() {
    int* ptr = (int*)malloc(sizeof(int));
    if (ptr == NULL) {
        return; // 处理分配失败
    }
    
    *ptr = 42;
    printf("值: %d\n", *ptr);
    
    free(ptr); // 始终释放分配的内存
    ptr = NULL; // 防止悬空指针
}

// 内存泄漏检测模式
void safeAllocation() {
    int* ptr = NULL;
    
    ptr = (int*)malloc(sizeof(int));
    if (ptr == NULL) {
        printf("分配失败\n");
        return;
    }
    
    // 使用内存
    *ptr = 100;
    
    // 清理
    free(ptr);
    ptr = NULL;
}

int main() {
    goodFunction();
    safeAllocation();
    return 0;
}
```
</UniversalEditor>

## 5. 常见陷阱
- **内存泄漏**：忘记调用 `free()`
- **重复释放**：对已释放的内存再次调用 `free()`
- **悬空指针**：释放内存后仍使用指针
- **缓冲区溢出**：写入超出分配内存的范围
- **空指针解引用**：未检查 `malloc()` 是否返回 NULL

## 6. 练习题
1. 编写一个函数，为字符串分配内存，复制给定字符串到其中，并返回指针。记得稍后释放它。
2. 创建一个函数，动态分配二维数组并用值初始化它。
3. 编写一个程序，通过跟踪分配和释放来演示内存泄漏检测。

## 7. 性能分析
- 手动内存管理可能比垃圾回收更高效
- 频繁的分配/释放可能导致内存碎片
- 内存池可以提高小规模频繁分配的性能
- 适当的内存管理对长时间运行的应用程序至关重要

---

> **小结**：C 的手动内存管理提供控制和性能，但需要纪律性。始终将 `malloc()` 与 `free()` 配对，检查分配失败，并使用 Valgrind 等工具检测内存泄漏。 